/*
Copyright 2024 - 2025 Zen HuiFer

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package clickhouse

import (
	"context"
	"fmt"
	"github.com/ClickHouse/clickhouse-go/v2"
	"github.com/ClickHouse/clickhouse-go/v2/lib/driver"
	"github.com/dop251/goja"
	"go.uber.org/zap"
	"iot-transmit/common"
	"net"
	"strings"
	"sync"
	"time"
)

// 全局map，用于存储id和ClickHouse连接的映射
var clickHouseClientMap = make(map[string]driver.Conn)

// 用于同步的互斥锁
var mu sync.Mutex

// GetClickHouse 创建或获取ClickHouse连接
func GetClickHouse(id string, addr []string, database, username, password string) (driver.Conn, error) {
	mu.Lock()
	defer mu.Unlock()

	session, exists := clickHouseClientMap[id]

	if exists {
		err := session.Ping(context.Background())
		if err != nil {
			// 如果连接出现问题，则移除并重新创建
			delete(clickHouseClientMap, id)
		} else {
			zap.S().Info("存在啦直接获取^_^")
			zap.S().Infof("Reusing existing ClickHouse connection for id: %s.\n", id)
			return session, nil
		}
	}

	conn, err := clickhouse.Open(&clickhouse.Options{
		Addr: addr,
		Auth: clickhouse.Auth{
			Database: database,
			Username: username,
			Password: password,
		},
		DialContext: func(ctx context.Context, addr string) (net.Conn, error) {
			var d net.Dialer
			return d.DialContext(ctx, "tcp", addr)
		},
		Debug: false,
		Debugf: func(format string, v ...any) {
			zap.S().Infof(format+"\n", v...)
		},
		Settings: clickhouse.Settings{
			"max_execution_time": 60,
		},
		Compression: &clickhouse.Compression{
			Method: clickhouse.CompressionLZ4,
		},
		DialTimeout:          time.Second * 30,
		MaxOpenConns:         5,
		MaxIdleConns:         5,
		ConnMaxLifetime:      time.Duration(10) * time.Minute,
		ConnOpenStrategy:     clickhouse.ConnOpenInOrder,
		BlockBufferSize:      10,
		MaxCompressionBuffer: 10240,
		ClientInfo: clickhouse.ClientInfo{
			Products: []struct {
				Name    string
				Version string
			}{
				{Name: "my-app", Version: "0.1"},
			},
		},
	})
	if err != nil {
		zap.S().Errorf("Failed to create ClickHouse connection", zap.Error(err))
		return nil, err
	}
	err = conn.Ping(context.Background())
	if err != nil {
		zap.S().Errorf("Failed to create ClickHouse connection", zap.Error(err))
		return nil, err
	}
	clickHouseClientMap[id] = conn
	zap.S().Infof("New ClickHouse connection for id %d has been initiated", id)
	return conn, nil
}

type ClickhouseOp struct{}

func (op *ClickhouseOp) HandleDataRowLists(

	table, Script string, dataRowList []common.DataRowList, client driver.Conn) error {
	res := op.RunScript(dataRowList, Script)

	var sqlStatements []string
	for _, re := range res {
		sqlStatements = append(sqlStatements, op.Save(re, table))
	}

	for _, statement := range sqlStatements {
		err := client.Exec(context.Background(), statement)
		if err != nil {
			zap.S().Errorf("执行SQL语句失败：%s", statement)
		}
	}

	return nil
}

func (op *ClickhouseOp) Save(dt []ClickhouseParam, table string) string {
	var fields []string
	var valueStrs []string
	for _, param := range dt {
		fields = append(fields, "`"+param.FieldName+"`")        // 使用反引号包围字段名
		valueStrs = append(valueStrs, buildValueStr(param.Value)) // 使用自定义函数处理值的格式化
	}

	fieldStr := strings.Join(fields, ", ")
	valueStr := strings.Join(valueStrs, ", ")

	query := fmt.Sprintf("INSERT INTO %s (%s) VALUES (%s);", table, fieldStr, valueStr)
	zap.S().Infof("[sql] %s",query)
	return query
}

func buildValueStr(value interface{}) string {
	switch v := value.(type) {
	case string:
		return "'" + v + "'"
	case int, int64, float64, bool:
		return fmt.Sprintf("%v", value)
	default:
		// 对于其他类型，这里可以添加更多的处理逻辑
		return "NULL"
	}
}

func (op *ClickhouseOp) RunScript(dataRowList []common.DataRowList, script string) [][]ClickhouseParam {

	vm := goja.New()
	_, err := vm.RunString(script)
	if err != nil {
		zap.S().Errorf("JS代码有问题！")
		return nil
	}
	var fn func(string2 []common.DataRowList) [][]ClickhouseParam
	get := vm.Get("main")
	if get != nil {

	err = vm.ExportTo(get, &fn)
	if err != nil {
		zap.S().Errorf("Js函数映射到 Go 函数失败！")
		return nil
	}
	a := fn(dataRowList)
	return a
	}
return  nil
}

type ClickhouseParam struct {
	FieldName string
	Value     interface{}
}
